#!/bin/bash

#
# pg_pitr
#
# Henry Molina
# hmolinab at unal dot edu dot co
#

VERSION=6

PGCFG="$PGDATA/postgresql.conf"
PGCFGRESTORE="$PGDATA/recovery.conf"
PITRCFG="$PGDATA/pg_pitr.conf"

#
# Functions
#

function usage() {
echo 'usage:'
echo "$0 --backup|--recovery [-config /path/to/pg_pitr.conf]"
echo "
Please read carefully
---------------------
1. Choose the way to perform the cluster's backup (cp $PGDATA /to/someplace, 
   tar, rsync, cpio, ...) and where it will be stored, put it in 
   backup_command option at $PITRCFG file.
   The about command will be execute to save a consisten cluster's copy ($PGDATA).
2. Choose the way to perform the WAL's backup ($PGDATA/pg_xlog) and where it
   will be stored, put it in archive_command at postgresql.conf file.
3. Choose the way to restore the clusters's backup. Put the related 
   command into recovery_command option at $PITRCFG file if you need it.
4. If you need to execute some command to put WAL files at pg_xlog when the 
   postgres go up after cluster's recovery then put it in restore_command 
   option at $PITRCFG. pgpitr will be place the about command at 
   recovery.conf file at recovery time for you.
5. See http://www.postgresql.org/docs/8.3/static/continuous-archiving.html
6. Check if listen_addresses parameter will be right at startup at new machine.
   Maybe recovery_command can help you to fix it.
7. The $PITRCFG file is created when this shell is run
6. Now you can call under postgres account:
   $0 --backup
"
exit 0
}

function log(){
	while read LINE; do
		if [ -f "$LOG" ]; then
			logger -f $LOG -t "pg_pitr" -- "$LINE"
		else
			logger -t "pg_pitr" -- "$LINE"
		fi
	done
}

function check_user(){
	if [ "${USER}" != "postgres" ]; then
		echo 'Are you the postgres user?'
		echo 'exit with error'
		exit 1
	fi
}

function ckeck_log(){
	LOG_DESTINATION=`awk -F\' '/log_destination/{print $2}' $PGCFG`
	if [ "$LOG_DESTINATION" == "syslog" ]; then
		LOG=''
	else
		LOG="$PGDATA/pg_log/pg_pitr.log"
		[ ! -f "$LOG" ] && touch $LOG
	fi
}

function check_pgdata(){
	if [ -z "$PGDATA" -o ! -d "$PGDATA" ]; then
		echo "Error: No PGDATA found at \$PGDATA [${PGDATA}]"
		echo "Have you created the cluster at PGDATA or are you exporting PGDATA?"
		echo 'exit with error'
		exit 1
	fi
}

function check_pitr_conf() {
	if [ ! -f $PITRCFG ]; then
		echo "Error: No $PITRCFG file found"
		echo 'Info: building a template...'
		cat > $PITRCFG <<EOF
#
# pg_pitr.conf
#

# Ideas:
# - No macro substitution available for backup_command or recovery_command (%p or %f)'
# - Exclude pg_xlog directory, its content will be copied with archive_command
# - backup_command will put the PostgreSQL's cluster in a safe place and with a consistent state
# - Test the backup_command


#backup_command=""
#backup_command="rsync -av --delete --exclude=pg_xlog/* --exclude=postmaster.pid \$PGDATA /tmp/pg"
#backup_command="rsync -azv --delete --exclude='pg_xlog' \$PGDATA/* rsync://server/path"
#backup_command="tar cvzf /path/to/backup/pg_pitr.tgz --exclude=pg_xlog \$PGDATA"


#recovery_command=""
#recovery_command="true"
#recovery_command="cp -a /path/from/backup/point \$PGDATA"
#recovery_command="tar xvzf /path/from/backup/pg_pitr.tgz -C \$PGDATA"

#clear_pg_xlog_afeter_recovery_command="no"

# The restore_command will be placed at recovery.conf file at recovery time,
# that means that you can use %f and %p macros here
# With restore_command PostgreSQL will get back the archive log files.
# After perform restore_command the WAL files must be at \$PGDATA/pg_xlog
restore_command=''
EOF
		exit 1
	fi
	sed -e 's/#.*//g' $PITRCFG > $PITRCFG.$$
	if ! fgrep -qs 'backup_command' $PITRCFG.$$; then
		echo "Error: No backup_command found at $PITRCFG"
		rm -f $PITRCFG.$$
		exit 1
	fi
	if ! fgrep -qs 'recovery_command' $PITRCFG.$$; then
		echo "Error: No recovery_command found at $PITRCFG"
		rm -f $PITRCFG.$$
		exit 1
	fi
	if ! fgrep -qs 'clear_pg_xlog_afeter_recovery_command' $PITRCFG.$$; then
		echo "Error: No clear_pg_xlog_afeter_recovery_command found at $PITRCFG"
		rm -f $PITRCFG.$$
		exit 1
	fi
	if ! fgrep 'clear_pg_xlog_afeter_recovery_command' $PITRCFG.$$ | egrep -qs 'yes|no' ; then
		echo "Error: bad clear_pg_xlog_afeter_recovery_command at $PITRCFG"
		rm -f $PITRCFG.$$
		exit 1
	fi
	if ! fgrep -qs 'restore_command' $PITRCFG.$$; then
		echo "Error: No restore_command found at $PITRCFG"
		rm -f $PITRCFG.$$
		exit 1
	fi
	rm -f $PITRCFG.$$
	if ! bash -n $PITRCFG &> /dev/null; then
		echo "Error: bad file $PITRCFG"
		echo 'exit with error'
		exit 1
	fi
}

function check_archive_command() {
	if ! sed -e 's/#.*//g' $PGCFG | fgrep -qs 'archive_command'; then
		echo "Error: No archive_command found at ${PGCFG}"
		echo 'exit with error'
		exit 1
	fi
	ARCHIVE_CMD=`sed -e 's/#.*//g' $PGCFG | fgrep 'archive_command' | awk -F\' '{print $2}' `
	if [ "$ARCHIVE_CMD" == '' ]; then
		echo "Error: archive_command at ${PGCFG} is empty"
		echo 'exit with error'
		exit 1
	fi
	echo "Warning: archive_command=${ARCHIVE_CMD}"
}

function check_archive_timeout() {
	if ! sed -e 's/#.*//g' $PGCFG | fgrep -qs 'archive_timeout'; then
		echo 'Warning: No archive_timeout found at $PGCFG. Is it right?'
	fi
}

function check_restore_command() {
	if [ ! -f $PGCFGRESTORE ]; then
		echo "Error: File $PGCFGRESTORE not found"
		echo 'exit with error'
		exit 1
	fi
	if ! sed -e 's/#.*//g' $PGCFGRESTORE | fgrep -qs 'restore_command'; then
		echo "Error: No restore_command found at $PGCFGRESTORE"
		echo 'exit with error'
		exit 1
	fi
}

function check_archive_mode() {
	if ! fgrep -qs 'archive_mode' $PGCFG; then
		VER=`pg_controldata | head -n1 | awk -F: '{print $2}' | xargs echo`
		if [ ${VER} -ge 830 ]; then
			echo "Error: No archive_mode found at \$PGCFG [$PGCFG]"
			echo 'exit with error'
			exit 1
		fi
		echo "Warning: No archive_mode found at \$PGCFG [$PGCFG]"
		return
	fi
	
       if ! sed -e 's/#.*//g' $PGCFG | fgrep -qs 'archive_mode'; then
                echo "Error: No archive_mode found at ${PGCFG}"
                echo 'exit with error'
                exit 1
        fi
        ARCHIVE_MODE=`sed -e 's/#.*//g' $PGCFG | fgrep 'archive_mode' | awk -F= '{print $2}' | xargs echo `
        if [ "$ARCHIVE_MODE" != 'on' ]; then
                echo "Error: archive_mode must be 'on' at ${PGCFG}"
		echo 'Warning: You must restart the service after changing archive_mode!'
                echo 'exit with error'
                exit 1
        fi
}

function do_backup() {
	#
	# Check for configurations
	#
	check_archive_command
	check_archive_mode
	check_archive_timeout
	check_pitr_conf

	#
	# Perform cluster backup
	#
	LABEL=`date +%Y%m%d%H%M`
	LABEL="pitr_backup_${LABEL}"
	pg_controldata
	echo "Starting ${LABEL}"
	pg_controldata 2>&1 | log
	if ! psql -a -c "SELECT pg_start_backup('$LABEL')"  2>&1 | log; then
		echo 'Error: No pg_start_backup() successful'
		echo "Error: see $LOG for details"
		exit 1
	fi
	trap "psql -a -c 'SELECT pg_stop_backup()'; exit 1" INT TERM EXIT
	echo "INFO: executing $backup_command" | log
	RET=0
	. $PITRCFG
	if ! $backup_command 2>&1 | log; then
		echo 'Error: executing backup_command'
		echo "Error: see $LOG for details"
		RET=1
	fi
	trap - INT TERM EXIT
	if ! psql -a -c "SELECT pg_stop_backup()" 2>&1 | log; then
		echo 'Error: pg_start_backup() return with errors'
		echo "Error: see $LOG for details"
		exit 1
	fi
	[ $RET -eq 1 ] && exit 1
}

function do_recovery() {
	#
	# Check for config files
	#
	check_pitr_conf

	#
	# checking for PostgreSQL
	#
	#??
	CHECK=0
	pidof postmaster &> /dev/null && CHECK=1
	pidof postgres &> /dev/null && CHECK=1
	psql -l &> /dev/null && CHECK=1
	ps xa | awk '{print $1}' | fgrep -qs `cat $PGDATA/postmaster.pid | head -n1` \
	    &> /dev/null && CHECK=1
	if [ $CHECK -eq 1 ]; then
	  echo 'Error: Postgresql is running ...'
	  echo 'Error: Check for postmaster or postgres process'
	  echo 'Error: To perform a recovery the postgres must be down'
	  echo 'exit with error'
	fi
	
	#
	# Perform recovery_command
	#
	. $PITRCFG
	[ -z "$recovery_command" ] && recovery_command=true
	if ! $recovery_command 2>&1 | log ; then
		echo 'Error: can not execute recovery_command'
		echo "Error: see $LOG for details"
		exit 1
	fi
	[ "$clear_pg_xlog_afeter_recovery_command" == "yes" ] && rm -rf $PGDATA/pg_xlog 
	mkdir -p $PGDATA/pg_xlog/archive_status &> /dev/null
	chmod 700 $PGDATA/pg_xlog 

	#
	# Create recovery.conf file
	#
	[ -z "$restore_command" ] && restore_command=true
	echo "restore_command='$restore_command'" > $PGCFGRESTORE
	check_restore_command

        #
        # Save pg_xlog files names
        #
        XLOG=`mktemp`
        cd ${PGDATA}/pg_xlog
        ls -1 ???????????????????????? > $XLOG 2> /dev/null
	[ $? -ne 0 ] && cat /dev/null > $XLOG
        cd -

	#
	# Start postmaster in local mode
	#
	chown postgres:postgres ${PGDATA} -R &> /dev/null
	if [ $? -ne 0 ]; then
		echo "Error: Is postgres the owner of \$PGDATA ?"
		exit 1
	fi
	echo 'NOTICE: starting postgres in sigle mode'
	postgres --single < /dev/null 
	if [  $? -ne 0 ]; then
		echo "Error: Recovery fault"
		echo "Error: check the restore_command at $PITRCFG"
		echo "Error: This command must put the <archive logs> at $PGDATA/pg_xlog"
		echo "Error: or put your self the <archives logs> at $PGDATA/pg_xlog"
		echo "Error: Do not forget to review postgres log"
		exit 1
	fi
	if [ -f "$PGDATA/recovery.conf" ]; then
		echo "Error: the file $PGDATA/recovery.conf is present at the system"
		echo "Error: There is a failure with the recovery"
	fi
	if [ -f "$PGDATA/recovery.done" ]; then
		echo 'NOTICE: Recovery succesful'
                echo 'NOTICE: Backing pg_xlog files'
                cd ${PGDATA}/pg_xlog
                while read FILE; do
                        mv $FILE '~${FILE}'
                done < $XLOG
		echo 'NOTICE: You can start PostgreSQL sevice now'
	else
		echo 'Error: Recovery proccess ending with problems'
	fi
        rm -f $XLOG
	echo 'NOTICE: Check postgres log for details'
	echo "NOTICE: Check $LOG log for details"
	echo "NOTICE: Check listen_addresses"
	echo 'Warning: Disabling archive_command!'
	sed -i -e 's/archive_command/#archive_command/g' $PGCFG
	sed -i -e 's/archive_mode/#archive_mode/g' $PGCFG
}

#
# Main
#

if [ $# -ne 1 ]; then
	usage
fi

#
# Task
#
case "$1" in
	"--backup")
		TASK='BACKUP'
		;;
	"--recovery")
		TASK='RECOVERY'
		if [ ! -z "$2" -a "$2" == "-config" ]; then
			if [ ! -z $3 -a -f $3]; then
				PITRCFG=$3
			else
				echo "Error: bad path to pg_pitr.conf file"
				exit 1
			fi
		fi
		;;
	"--version")
		echo "$0 version: $VERSION"
		exit 0
		;;
	*)
		usage
		;;
esac

check_user
check_pgdata
ckeck_log

[ "$TASK" == "BACKUP" ] && do_backup
[ "$TASK" == "RECOVERY" ] && do_recovery

echo "Elapsed time: ${SECONDS} seconds" | log
exit 0


